﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Browser;
using System.Text;
using System.Xml.Linq;
using SharePhone.Common;

namespace SharePhone.Provider.WebServices
{
    public delegate void RequestCompleteHandler(object sender, RequestCompleteEventArgs e);

    public abstract class BaseWebService
    {
        private static string soapEnvelope =
            @"<soap:Envelope
                            xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
                            xmlns:xsd='http://www.w3.org/2001/XMLSchema'
                            xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
                    <soap:Body></soap:Body></soap:Envelope>";

        private XDocument soapEnvelopeXml;
        public string BaseUrl { get; set; }
        internal abstract string GetUrl();

        internal void GetResponse(string action, string soapContent, ICredentials credentials,
                                  RequestCompleteHandler callback)
        {
            soapEnvelopeXml = CreateSoapEnvelope(soapContent);
            HttpWebRequest webRequest = CreateWebRequest(GetUrl(), action);
            if (credentials != null)
                webRequest.Credentials = credentials;
            InsertSoapEnvelopeIntoWebRequest(webRequest, callback);
        }

        private XDocument CreateSoapEnvelope(string content)
        {
            var sb = new StringBuilder(soapEnvelope);
            sb.Insert(sb.ToString().IndexOf("</soap:Body>"), content);

            // create an empty soap envelope
            XDocument soapEnvelopeXml = XDocument.Parse(sb.ToString());
            
            return soapEnvelopeXml;
        }

        private HttpWebRequest CreateWebRequest(string url, string action)
        {
            //url = url.Replace("//", "");  
            HttpWebRequest.RegisterPrefix("http://", WebRequestCreator.ClientHttp);
            HttpWebRequest.RegisterPrefix("https://", WebRequestCreator.ClientHttp);
            Uri serviceUrl = new Uri(url, UriKind.Absolute);
            var webRequest = WebRequest.CreateHttp(serviceUrl);
            webRequest.Headers["SOAPAction"] = action;
            webRequest.ContentType = "text/xml;charset=\"utf-8\"";
            webRequest.Accept = "text/xml";
            webRequest.Method = "POST";
            return webRequest;
        }

        private void InsertSoapEnvelopeIntoWebRequest(HttpWebRequest webRequest, RequestCompleteHandler callback)
        {

            IAsyncResult result = webRequest.BeginGetRequestStream(SendSoapEnvelope,
                                                                   new UserState {
                                                                       WebRequest = webRequest,
                                                                       CallbackMethod = callback
                                                                   });
        }

        private void SendSoapEnvelope(IAsyncResult ar)
        {
            HttpWebRequest request = ((UserState) ar.AsyncState).WebRequest;
            Stream postStream = request.EndGetRequestStream(ar);
            soapEnvelopeXml.Save(postStream);
            postStream.Close();
            request.BeginGetResponse(ResponseCallback, ar.AsyncState);
        }

        private void ResponseCallback(IAsyncResult ar)
        {
            HttpWebRequest request = ((UserState) ar.AsyncState).WebRequest;
            try
            {
                var response = (HttpWebResponse) request.EndGetResponse(ar);
                string soapResult = string.Empty;
                using (var rd = new StreamReader(response.GetResponseStream()))
                {
                    soapResult = rd.ReadToEnd();
                }
                response.Close();
                XDocument doc = XDocument.Parse(soapResult);                
                ((UserState) ar.AsyncState).CallbackMethod(this, new RequestCompleteEventArgs(doc.FirstNode as XElement));
            }
            catch (WebException ex)
            {
                string faultString = string.Empty;
                try
                {
                    using (var stream = new StreamReader((ex.Response as HttpWebResponse).GetResponseStream()))
                    {
                        string error = stream.ReadToEnd();
                        XDocument errorDoc = XDocument.Parse(error);
                        faultString = errorDoc.Descendants("faultstring").FirstOrDefault().Value;
                    }
                }
                catch
                {
                    faultString = "<No error description available>";
                }
                ((UserState) ar.AsyncState).CallbackMethod(this,
                                                           new RequestCompleteEventArgs
                                                               {
                                                                   Error = ex,
                                                                   ErrorDescription = faultString,
                                                                   Cancelled = true
                                                               });
            }
        }

        protected NetworkCredential GetNetworkCredential(CredentialSettings credentialSettings)
        {
            if (credentialSettings != null)
            {
                return new NetworkCredential
                           {
                               Domain = credentialSettings.Domain,
                               UserName = credentialSettings.UserName,
                               Password = credentialSettings.Password
                           };
            }
            return null;
        }
    }

    internal class UserState
    {
        public HttpWebRequest WebRequest { get; set; }
        public RequestCompleteHandler CallbackMethod { get; set; }
    }
}